package cn.always.xiajia.framework.web.core.handler;

import java.util.Objects;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;

import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;

import cn.always.xiajia.framework.common.entity.AjaxResult;
import cn.always.xiajia.framework.common.exception.ServiceException;
import cn.always.xiajia.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.always.xiajia.framework.common.web.WebFrameworkUtils;
import cn.hutool.core.exceptions.ExceptionUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 全局异常处理器，将 Exception 翻译成 AjaxResult + 对应的异常编号
 *
 * @author 芋道源码
 */
@RestControllerAdvice
@AllArgsConstructor
@Slf4j
public class GlobalExceptionHandler {

	/**
	 * 处理所有异常，主要是提供给 Filter 使用 因为 Filter 不走 SpringMVC
	 * 的流程，但是我们又需要兜底处理异常，所以这里提供一个全量的异常处理过程，保持逻辑统一。
	 *
	 * @param request 请求
	 * @param ex 异常
	 * @return 通用返回
	 */
	public AjaxResult allExceptionHandler(HttpServletRequest request, Throwable ex) {
		if (ex instanceof MissingServletRequestParameterException) {
			return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
		}
		if (ex instanceof MethodArgumentTypeMismatchException) {
			return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
		}
		if (ex instanceof MethodArgumentNotValidException) {
			return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex);
		}
		if (ex instanceof BindException) {
			return bindExceptionHandler((BindException) ex);
		}
		if (ex instanceof ConstraintViolationException) {
			return constraintViolationExceptionHandler((ConstraintViolationException) ex);
		}
		if (ex instanceof ValidationException) {
			return validationException((ValidationException) ex);
		}
		if (ex instanceof NoHandlerFoundException) {
			return noHandlerFoundExceptionHandler(request, (NoHandlerFoundException) ex);
		}
		if (ex instanceof HttpRequestMethodNotSupportedException) {
			return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
		}
		if (ex instanceof ServiceException) {
			return serviceExceptionHandler((ServiceException) ex);
		}
		if (ex instanceof AccessDeniedException) {
			return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
		}
		if (ex instanceof HttpMessageNotReadableException) {
			return httpMessageNotReadableExceptionHandler((HttpMessageNotReadableException) ex);
		}
		if (ex instanceof HttpMessageNotReadableException) {
			return httpMessageNotReadableExceptionHandler((HttpMessageNotReadableException) ex);
		}
		return defaultExceptionHandler(request, ex);
	}

	/**
	 * 处理 SpringMVC 请求参数缺失
	 *
	 * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
	 */
	@ExceptionHandler(value = HttpMessageNotReadableException.class)
	public AjaxResult httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException ex) {
		log.warn("[httpMessageNotReadableExceptionHandler]", ex);
		return AjaxResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), ex.getMessage());
	}

	/**
	 * 处理 SpringMVC 请求参数缺失
	 *
	 * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
	 */
	@ExceptionHandler(value = MissingServletRequestParameterException.class)
	public AjaxResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
		log.warn("[missingServletRequestParameterExceptionHandler]", ex);
		return AjaxResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
	}

	/**
	 * 处理 SpringMVC 请求参数类型错误
	 *
	 * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
	 */
	@ExceptionHandler(MethodArgumentTypeMismatchException.class)
	public AjaxResult methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
		log.warn("[missingServletRequestParameterExceptionHandler]", ex);
		return AjaxResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
	}

	/**
	 * 处理 SpringMVC 参数校验不正确
	 */
	@ExceptionHandler(MethodArgumentNotValidException.class)
	public AjaxResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
		log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
		FieldError fieldError = ex.getBindingResult().getFieldError();
		assert fieldError != null; // 断言，避免告警
		return AjaxResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
				String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
	}

	/**
	 * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验
	 */
	@ExceptionHandler(BindException.class)
	public AjaxResult bindExceptionHandler(BindException ex) {
		log.warn("[handleBindException]", ex);
		FieldError fieldError = ex.getFieldError();
		assert fieldError != null; // 断言，避免告警
		return AjaxResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
				String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
	}

	/**
	 * 处理 Validator 校验不通过产生的异常
	 */
	@ExceptionHandler(value = ConstraintViolationException.class)
	public AjaxResult constraintViolationExceptionHandler(ConstraintViolationException ex) {
		log.warn("[constraintViolationExceptionHandler]", ex);
		ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
		return AjaxResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
				String.format("请求参数不正确:%s", constraintViolation.getMessage()));
	}

	/**
	 * 处理 Dubbo Consumer 本地参数校验时，抛出的 ValidationException 异常
	 */
	@ExceptionHandler(value = ValidationException.class)
	public AjaxResult validationException(ValidationException ex) {
		log.warn("[constraintViolationExceptionHandler]", ex);
		// 无法拼接明细的错误信息，因为 Dubbo Consumer 抛出 ValidationException
		// 异常时，是直接的字符串信息，且人类不可读
		return AjaxResult.error(GlobalErrorCodeConstants.BAD_REQUEST);
	}

	/**
	 * 处理 SpringMVC 请求地址不存在
	 *
	 * 注意，它需要设置如下两个配置项： 1. spring.mvc.throw-exception-if-no-handler-found 为 true
	 * 2. spring.mvc.static-path-pattern 为 /statics/**
	 */
	@ExceptionHandler(NoHandlerFoundException.class)
	public AjaxResult noHandlerFoundExceptionHandler(HttpServletRequest req, NoHandlerFoundException ex) {
		log.warn("[noHandlerFoundExceptionHandler]", ex);
		return AjaxResult.error(GlobalErrorCodeConstants.NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
	}

	/**
	 * 处理 SpringMVC 请求方法不正确
	 *
	 * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
	 */
	@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
	public AjaxResult httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
		log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
		return AjaxResult.error(GlobalErrorCodeConstants.METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
	}

	/**
	 * 处理 Resilience4j 限流抛出的异常
	 */
	public AjaxResult requestNotPermittedExceptionHandler(HttpServletRequest req, Throwable ex) {
		log.warn("[requestNotPermittedExceptionHandler][url({}) 访问过于频繁]", req.getRequestURL(), ex);
		return AjaxResult.error(GlobalErrorCodeConstants.TOO_MANY_REQUESTS);
	}

	/**
	 * 处理 Spring Security 权限不足的异常
	 *
	 * 来源是，使用 @PreAuthorize 注解，AOP 进行权限拦截
	 */
	@ExceptionHandler(value = AccessDeniedException.class)
	public AjaxResult accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
		log.warn("[accessDeniedExceptionHandler][userCode({}) 无法访问 url({})]", WebFrameworkUtils.getRqLoginUserCode(req),
				req.getRequestURL(), ex);
		return AjaxResult.error(GlobalErrorCodeConstants.FORBIDDEN);
	}

	/**
	 * 处理业务异常 ServiceException
	 *
	 * 例如说，商品库存不足，用户手机号已存在。
	 */
	@ExceptionHandler(value = ServiceException.class)
	public AjaxResult serviceExceptionHandler(ServiceException ex) {
		log.info("[serviceExceptionHandler]", ex);
		return AjaxResult.error(ex.getCode(), ex.getMessage());
	}

	/**
	 * 处理系统异常，兜底处理所有的一切
	 */
	@ExceptionHandler(value = Exception.class)
	public AjaxResult defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
		// 情况一：处理表不存在的异常
		AjaxResult tableNotExistsResult = handleTableNotExists(ex);
		if (tableNotExistsResult != null) {
			return tableNotExistsResult;
		}

		// 情况二：部分特殊的库的处理
		if (Objects.equals("io.github.resilience4j.ratelimiter.RequestNotPermitted", ex.getClass().getName())) {
			return requestNotPermittedExceptionHandler(req, ex);
		}

		// 情况三：处理异常
		log.error("[defaultExceptionHandler]", ex);

		// 插入异常日志暂时不开发

		// 返回 ERROR AjaxResult
		return AjaxResult.error(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
	}

	/**
	 * 处理 Table 不存在的异常情况
	 *
	 * @param ex 异常
	 * @return 如果是 Table 不存在的异常，则返回对应的 AjaxResult
	 */
	private AjaxResult handleTableNotExists(Throwable ex) {
		String message = ExceptionUtil.getRootCauseMessage(ex);
		if (!message.contains("doesn't exist")) {
			return null;
		}
		// 1. 数据报表
		if (message.contains("sys_")) {
			log.error("系统模块数据库表未导入【sys】");
			return AjaxResult.error(GlobalErrorCodeConstants.NOT_IMPLEMENTED.getCode(), "系统模块数据库表未导入【sys】");
		}

		return null;
	}

}
